function prep_face_to_orl()
% 单张或整目录，将任意人脸照片→ORL风格：112x92 灰度、对齐、PGM
% 运行后先弹出“选择文件或文件夹”的对话框

clc; close all;

choice = questdlg('选择处理方式', '预处理', ...
    '单张图片','批量文件夹','取消','单张图片');
if strcmp(choice,'取消'), return; end

if strcmp(choice,'单张图片')
    [fp, pth] = uigetfile({'*.*','All images'}, '选择一张人脸照片');
    if isequal(fp,0), return; end
    in_path  = fullfile(pth,fp);
    [out_fp, out_pth] = uiputfile({'*.pgm','PGM';'*.png','PNG';'*.jpg','JPG'}, ...
                                   '保存为', fullfile(pth, replace_ext(fp,'pgm')));
    if isequal(out_fp,0), return; end
    out_path = fullfile(out_pth,out_fp);

    Iout = convert_one(in_path);
    imwrite(to_uint8(Iout), out_path);
    figure; imshow(Iout,[]); title("已保存为: " + out_path, 'Interpreter','none');

else
    in_dir = uigetdir(pwd, '选择输入文件夹（将遍历其中所有图片）');
    if isequal(in_dir,0), return; end
    out_dir = uigetdir(pwd, '选择输出文件夹');
    if isequal(out_dir,0), return; end

    exts = {'.jpg','.jpeg','.png','.bmp','.tif','.tiff','.pgm'};
    files = dir(fullfile(in_dir,'**','*.*'));
    files = files(~[files.isdir]);

    cnt_ok=0; cnt_fail=0;
    for i=1:numel(files)
        [~,name,ext] = fileparts(files(i).name);
        if ~ismember(lower(ext), exts), continue; end
        in_path = fullfile(files(i).folder, files(i).name);
        try
            Iout = convert_one(in_path);
            rel   = erase(in_path, in_dir);
            rel   = rel(2:end); % 去掉前置分隔符
            [rel_dir, base, ~] = fileparts(rel);
            out_subdir = fullfile(out_dir, rel_dir);
            if ~exist(out_subdir,'dir'), mkdir(out_subdir); end
            out_path = fullfile(out_subdir, [base '.pgm']);
            imwrite(to_uint8(Iout), out_path);
            cnt_ok = cnt_ok+1;
        catch ME
            warning("失败：%s -> %s", in_path, ME.message);
            cnt_fail = cnt_fail+1;
        end
    end
    fprintf('完成：成功 %d，失败 %d。输出目录：%s\n', cnt_ok, cnt_fail, out_dir);
end
end

%% ============= 核心：把任意人脸转成 112x92 的 ORL 风格灰度图 =============
function Iout = convert_one(in_path)
% 返回 double 灰度图，大小 112x92，值域[0,1]
imgH = 112; imgW = 92; target_ratio = imgH/imgW; % 1.2174...

I = imread(in_path);
if size(I,3)>1, I = rgb2gray(I); end
I = im2double(I);

% 1) 检测人脸
fdet = vision.CascadeObjectDetector('FrontalFaceCART', 'MergeThreshold', 5);
bboxes = step(fdet, im2uint8(I));
if isempty(bboxes)
    % 退化策略：全图居中裁剪到目标比例
    Icrop = center_crop_to_ratio(I, target_ratio);
else
    % 选最大的人脸框
    [~,idx] = max(bboxes(:,3).*bboxes(:,4));
    box = bboxes(idx,:); % [x y w h]

    % 2) 检测双眼以做旋转对齐（若失败则跳过旋转）
    try
        edet = vision.CascadeObjectDetector('EyePairBig');
        ebox = step(edet, im2uint8(I));
        if ~isempty(ebox)
            % 取与脸框重叠最大的眼区
            ov = bboxOverlapRatio(ebox, box);
            [~,ei] = max(ov);
            e = ebox(ei,:); % 眼对区域
            % 简单估计左右眼中心（区域上半部的一左一右）
            eyeROI = imcrop(I, e);
            BW = imbinarize(adapthisteq(eyeROI));
            [y,x] = find(BW);
            if ~isempty(x)
                x = x + e(1); y = y + e(2);
                cx = median(x); cy = median(y);
                % 以眼区中心与脸中心连线估计倾角（粗略但够用）
                fc = [box(1)+box(3)/2, box(2)+box(4)/2];
                ang = atan2d(cy - fc(2), cx - fc(1));
                I = imrotate(I, -ang, 'bilinear','crop');
                % 旋转后重新测一次脸框
                bboxes2 = step(fdet, im2uint8(I));
                if ~isempty(bboxes2)
                    [~,idx2] = max(bboxes2(:,3).*bboxes2(:,4));
                    box = bboxes2(idx2,:);
                end
            end
        end
    catch
        % 眼睛检测失败就不旋转
    end

    % 3) 以脸框为中心，扩张一点边缘，再按目标比例裁剪
    scale = 1.15; % 稍微加大一点，保留边缘
    cx = box(1)+box(3)/2; cy = box(2)+box(4)/2;
    w  = box(3)*scale;    h  = box(4)*scale;

    % 调整 w/h 使得 h/w=target_ratio
    if h/w < target_ratio
        h = w*target_ratio;
    else
        w = h/target_ratio;
    end
    x1 = round(cx - w/2); y1 = round(cy - h/2);
    rect = [x1, y1, round(w), round(h)];

    Ipad = padarray(I, [200 200], 'replicate','both'); % 防越界
    rect = rect + [200 200 0 0];
    Icrop = imcrop(Ipad, rect);
end

% 4) 统一尺寸 + 对比度
Icrop = imresize(Icrop, [imgH imgW], 'bilinear');
Icrop = adapthisteq(Icrop); % 自适应直方图均衡，增强对比度

Iout = Icrop; % double、[0,1]、112x92
end

%% ============= 工具函数 =============
function I2 = center_crop_to_ratio(I, target_ratio)
[h,w] = size(I);
cur_ratio = h/w;
if cur_ratio > target_ratio
    % 太高：裁掉上下
    new_h = round(w*target_ratio);
    y1 = floor((h-new_h)/2)+1;
    I2 = I(y1:y1+new_h-1, :);
else
    % 太宽：裁掉左右
    new_w = round(h/target_ratio);
    x1 = floor((w-new_w)/2)+1;
    I2 = I(:, x1:x1+new_w-1);
end
end

function u8 = to_uint8(I)
% double[0,1] -> uint8
u8 = im2uint8(max(0,min(1,I)));
end

function name2 = replace_ext(name, newext)
[~,n,~]=fileparts(name); name2=[n,'.',newext];
end
